import * as path from "path";
import { readFileSync } from "node:fs";
import ts from "typescript";
import Handlebars from "handlebars";
import { existsSync, mkdirSync, readdirSync, writeFileSync, statSync } from "fs";
import { HvigorNode, HvigorPlugin } from "@ohos/hvigor";

const PLUGIN_ID = "FWRouterHvigorPlugin";
const ROUTER_BUILDER_PATH = "src/main/ets/generated";
const ROUTER_BUILDER_NAME = "RouterBuilder.ets";
const ROUTER_ANNOTATION_NAME = "NavigationRoute";
const ROUTER_BUILDER_TEMPLATE = "viewBuilder.tpl";

export function routerHvigorPlugin(pluginConfig: PluginConfig): HvigorPlugin {
  if (!pluginConfig) {
    pluginConfig = {}
  }
  pluginConfig.annotation = ROUTER_ANNOTATION_NAME;
  pluginConfig.builderTpl = ROUTER_BUILDER_TEMPLATE;
  pluginConfig.builderDir = ROUTER_BUILDER_PATH;
  pluginConfig.builderFileName = ROUTER_BUILDER_NAME;
  return {
    pluginId: PLUGIN_ID,
    apply(node: HvigorNode) {
      console.log(`Exec ${PLUGIN_ID}...${__dirname}`);
      console.log(`node:${node.getNodeName()},nodePath:${node.getNodePath()}}`);
      // 获取模块名
      pluginConfig.moduleName = node.getNodeName();
      // 获取模块路径
      pluginConfig.modulePath = node.getNodePath();
      if (!pluginConfig.scanFiles) {
        pluginConfig.scanFiles = getFilesWithExtension(node.getNodePath() + '/src/main/ets', '.ets')
      }
      console.log(`pluginConfig.scanFiles=${pluginConfig.scanFiles}`)

      pluginExec(pluginConfig);
    }
  }
}

function getFilesWithExtension(folderPath: string, extension: string): string[] {
  let files: string[] = [];

  const walk = (currentPath: string) => {
    const list = readdirSync(currentPath);
    list.forEach(file => {
      const filePath = path.join(currentPath, file);
      const stat = statSync(filePath);
      if (stat && stat.isDirectory()) {
        walk(filePath);
      } else {
        if (path.extname(file) === extension) {
          files.push(filePath);
        }
      }
    });
  };

  walk(folderPath);
  return files;
}

class NodeInfo {
  value?: any;
}

interface TemplateModel {
  viewList: ViewInfo[];
}

enum DecoratorParseState {
  idle, // 空闲
  foundDecorator, // 已找到自定义装饰器
  parsedParams, // 已解析装饰器参数
  foundStruct // 已找到`struct`关键字
}

// 用于生成组件注册类
class ViewInfo {
  // 路由名，自定义装饰器中配置的参数值
  name?: string;
  // 自定义组件的名字
  viewName?: string;
  // import路径
  importPath?: string;
  // 组件注册方法名
  functionName?: string;
  // builder方法声明中是否有参数
  paramsDefine?: string;
  // viewName实例化时的参数传值
  paramsUse?: string;
}

// 配置文件，在hvigor中配置
export class PluginConfig {
  // 注册路由的方法的文件名
  builderFileName?: string;
  // 注册路由的方法的文件路径
  builderDir?: string;
  // 模块名
  moduleName?: string;
  // 模块路径
  modulePath?: string;
  // 装饰器名称
  annotation?: string;
  // 扫描的文件路径
  scanFiles?: string[];
  // 生成代码模板
  builderTpl?: string;
}

// 文件解析结果
class AnalyzeResult {
  // 加载的组件名
  viewName?: string;
  // 组件注册方法名
  functionName?: string;
  // 路由中配置的路径
  name?: string;
  // 路由中传递的参数
  paramsDefine?: string;
  paramsUse?: string;
}

// ets文件解析
export class EtsAnalyzer {
  // 文件路径
  sourcePath: string;
  // hvigor配置
  pluginConfig: PluginConfig;
  // 解析结果
  analyzeResult: AnalyzeResult = new AnalyzeResult();
  // 关键字位置
  keywordPos: number = 0;
  // 是否存在装饰器
  routerAnnotationExisted: boolean = false;
  // 当前的解析进度
  decoratorParseState: DecoratorParseState = DecoratorParseState.idle

  constructor(pluginConfig: PluginConfig, sourcePath: string) {
    this.pluginConfig = pluginConfig;
    this.sourcePath = sourcePath;
  }

  start() {
    console.log('ETS文件解析器：' + '开始解析文件：' + this.sourcePath);
    // 读取文件
    const sourceCode = readFileSync(this.sourcePath, "utf-8");
    // 解析文件，生成节点树信息
    const sourceFile = ts.createSourceFile(this.sourcePath, sourceCode, ts.ScriptTarget.ES2021, false);
    // 遍历节点信息
    console.log('ETS文件解析器：' + '遍历节点信息，总节点数：' + sourceFile.getChildCount());
    ts.forEachChild(sourceFile, (node: ts.Node) => {
      console.log('ETS文件解析器：' + '循环解析节点');
      // 解析节点
      this.resolveNode(node);
    });
  }

  resolveNode(node: ts.Node): NodeInfo | undefined {
    console.log('ETS文件解析器：' + '开始解析节点：' + node.toString());
    switch (node.kind) {
    // 未知的声明节点
      case ts.SyntaxKind.MissingDeclaration:
        this.resolveMissDeclaration(node);
        break;
    // 装饰器节点
      case ts.SyntaxKind.Decorator:
        this.resolveDecoration(node);
        break;
    // 表达式节点
      case ts.SyntaxKind.ExpressionStatement:
        this.resolveExpression(node);
        break;
    // 标识符节点
      case ts.SyntaxKind.Identifier:
        return this.resolveIdentifier(node);
        break;
    }
  }

  // 未知节点，则继续解析子节点
  resolveMissDeclaration(node: ts.Node) {
    console.log('ETS文件解析器：' + '未知节点，则继续解析子节点');
    node.forEachChild((cnode) => {
      console.log('ETS文件解析器：' + '循环解析节点');
      this.resolveNode(cnode);
    })
  }

  // 解析装饰器
  resolveDecoration(node: ts.Node) {
    console.log('ETS文件解析器：' + '开始解析装饰器节点');
    // 转换为装饰器节点类型
    let decorator = node as ts.Decorator;
    // 判断表达式是否是函数调用
    if (decorator.expression.kind === ts.SyntaxKind.CallExpression) {
      console.log('ETS文件解析器：' + '该装饰器是函数调用');
      const callExpression = decorator.expression as ts.CallExpression;
      // 表达式类型是否是标识符
      if (callExpression.expression.kind === ts.SyntaxKind.Identifier) {
        console.log('ETS文件解析器：' + '表达式类型是标识符');
        const identifier = callExpression.expression as ts.Identifier;
        // 标识符是否是自定义的装饰器
        if (identifier.text === this.pluginConfig.annotation) {
          console.log('ETS文件解析器：' + `该装饰器${identifier.text}是自定义装饰器${this.pluginConfig.annotation}`);
          this.routerAnnotationExisted = true;
          this.decoratorParseState = DecoratorParseState.foundDecorator;
          const arg = callExpression.arguments[0];
          // 调用方法的第一个参数是否是对象{}表达式
          if (arg.kind === ts.SyntaxKind.ObjectLiteralExpression) {
            const properties = (arg as ts.ObjectLiteralExpression).properties;
            // 遍历装饰器中的所有参数
            console.log('ETS文件解析器：' + '遍历装饰器中的所有参数' + properties.toString());
            properties.forEach((propertie) => {
              if (propertie.kind === ts.SyntaxKind.PropertyAssignment) {
                // 参数是否是自定义装饰器中的变量名
                console.log('ETS文件解析器：' + `参数${(propertie.name as ts.Identifier).escapedText}=${(propertie.initializer as ts.StringLiteral).text}`);
                if ((propertie.name as ts.Identifier).escapedText === "routeName") {
                  // 将装饰器中的变量的值赋值给解析结果中的变量
                  this.analyzeResult.name = (propertie.initializer as ts.StringLiteral).text;
                }
                if ((propertie.name as ts.Identifier).escapedText === "hasParams") {
                  // 将装饰器中的变量的值赋值给解析结果中的变量
                  this.analyzeResult.paramsDefine = propertie.initializer.kind === ts.SyntaxKind.TrueKeyword ? "params: ESObject" : "";
                  this.analyzeResult.paramsUse = propertie.initializer.kind === ts.SyntaxKind.TrueKeyword ? "{ params: params }" : "";
                }
              }
            })
            this.decoratorParseState = DecoratorParseState.parsedParams;
          } else {
            console.log('ETS文件解析器：' + '调用方法的第一个参数不是对象表达式');
          }
        } else {
          console.log('ETS文件解析器：' + `该装饰器${identifier.text}不是自定义装饰器${this.pluginConfig.annotation}`);
        }
      } else {
        console.log('ETS文件解析器：' + '表达式类型不是标识符');
      }
    } else {
      console.log('ETS文件解析器：' + '该装饰器不是函数调用');
    }
  }

  // 解析函数
  resolveExpression(node: ts.Node) {
    console.log('ETS文件解析器：' + '开始解析表达式节点');
    let args = node as ts.ExpressionStatement;
    let identifier = this.resolveNode(args.expression);
    console.log('ETS文件解析器：' + `路由名${this.analyzeResult?.name}  标识符${identifier?.value}`);
    if (this.decoratorParseState == DecoratorParseState.parsedParams && identifier?.value === 'struct') {
      this.decoratorParseState = DecoratorParseState.foundStruct
      return
    }
    // 找到`struct`关键字后，后面一个ExpressionStatement就是组件名称
    if (this.decoratorParseState == DecoratorParseState.foundStruct) {
      this.analyzeResult.viewName = identifier?.value;
      let viewName: string = identifier?.value.toString();
      viewName = `${viewName.charAt(0).toLowerCase()}${viewName.slice(1, viewName.length)}`;
      this.analyzeResult.functionName = viewName;

      this.decoratorParseState = DecoratorParseState.idle
    }
  }

  // 解析表达式
  resolveIdentifier(node: ts.Node): NodeInfo {
    let identifier = node as ts.Identifier;
    let info = new NodeInfo();
    info.value = identifier.escapedText.toString();
    return info;
  }
}

/**
 * 解析插件开始执行
 * @param  {PluginConfig} config
 */
function pluginExec(config: PluginConfig) {
  console.log("plugin exec...");
  const templateModel: TemplateModel = {
    viewList: []
  };

  let isEntryModule = false

  // 遍历需要扫描的文件列表
  console.log('遍历需要扫描的文件列表');
  config.scanFiles?.forEach((file) => {
    console.log('正在处理：' + file);
    // 筛选Entry.ets文件
    if (isEntryEtsFile(file)) {
      isEntryModule = true;
      modifyEntryForImport(file)
    }

    // 文件绝对路径
    let sourcePath = path.isAbsolute(file) ? file : path.join(config.modulePath??'', file);
    if (!sourcePath.endsWith('.ets')) {
      sourcePath = sourcePath + '.ets';
    }
    console.log('文件绝对路径：' + sourcePath);
    // 获取文件相对路径
    const importPath = path.relative(`${config.modulePath}/${config.builderDir}`, sourcePath).replaceAll("\\", "/").replaceAll(".ets", "");
    console.log('文件相对路径：' + importPath);
    const analyzer = new EtsAnalyzer(config, sourcePath);
    // 开始解析文件
    analyzer.start();
    console.log(JSON.stringify(analyzer.analyzeResult));
    console.log(importPath);
    // 如果解析的文件中存在装饰器，则将结果保存到列表中
    if (analyzer.routerAnnotationExisted) {
      templateModel.viewList.push({
        name: analyzer.analyzeResult.name,
        viewName: analyzer.analyzeResult.viewName,
        importPath: importPath,
        functionName: analyzer.analyzeResult.functionName,
        paramsDefine: analyzer.analyzeResult.paramsDefine === undefined ? "" : analyzer.analyzeResult.paramsDefine,
        paramsUse: analyzer.analyzeResult.paramsUse === undefined ? "" : analyzer.analyzeResult.paramsUse
      });
    }

  })
  // 生成路由方法文件
  generateBuilderEtsFileWithTemplate(templateModel, config);
  // 非feature类型模块生成Index.ets文件
  if (!isEntryModule) {
    modifyIndexForExport(config);
  }
}

/**
 * 根据模板生成路由自动注册方法文件。
 *
 * @param {TemplateModel} templateModel
 * @param {PluginConfig} config
 */
function generateBuilderEtsFileWithTemplate(templateModel: TemplateModel, config: PluginConfig) {
  console.log('generateBuilder' + JSON.stringify(templateModel));
  const builderPath = path.resolve(__dirname, `../${config.builderTpl}`);
  const tpl = readFileSync(builderPath, { encoding: "utf8" });
  const template = Handlebars.compile(tpl);
  const output = template({
    viewList: templateModel.viewList
  });

  const routerBuilderDir = `${config.modulePath}/${config.builderDir}`;
  if (!existsSync(routerBuilderDir)) {
    mkdirSync(routerBuilderDir, { recursive: true });
  }
  writeFileSync(`${routerBuilderDir}/${config.builderFileName}`, output, { encoding: "utf8" });
}

/**
 * 修改Index.ets文件。
 *
 * 将动态生成的模板文件在Index.ets中export出去。
 *
 * 业务代码import了Index.ets文件后即可触发类装饰器，自动注册NavigationDestination路由。
 */
function modifyIndexForExport(config: PluginConfig) {
  const indexPath = `${config.modulePath}/Index.ets`;
  if (!existsSync(indexPath)) {
    writeFileSync(indexPath, '', 'utf-8');
  }
  let indexContent: string = readFileSync(indexPath, { encoding: "utf8" });
  if (!indexContent.includes(" * Copyright (c) 2024 Huawei Device Co., Ltd.")) {
    const licensesPath = path.resolve(__dirname, `../license.tpl`);
    const licenses: string = readFileSync(licensesPath, { encoding: "utf-8" });
    indexContent = licenses + "\n" + indexContent;
  }
  const indexArr: string[] = indexContent.split("\n");
  const indexArray: string[] = [];
  indexArr.forEach((value: string) => {
    if (!value.includes(config?.builderDir?.toString() ?? '')) {
      indexArray.push(value);
    }
  });
  indexArray.push(`export * from './${config.builderDir}/${config.builderFileName?.replace(".ets", "")}';`);
  writeFileSync(indexPath, indexArray.join("\n"), { encoding: "utf8" });
}

function isEntryEtsFile(indexPath: string): boolean {
  let indexContent: string = readFileSync(indexPath, { encoding: "utf8" });
  // 通过关键字判断
  return indexContent.includes('extends UIAbility {')
}

/**
 * 在entry.ets文件中import生成的文件
 * @param config
 */
function modifyEntryForImport(indexPath: string) {
  console.log('处理EntryAbility文件：' + indexPath);
  const importStatement = "import('../generated/RouterBuilder');";

  // 判断是否已import生成的文件
  let indexContent: string = readFileSync(indexPath, { encoding: "utf8" });
  if (indexContent.includes(importStatement)) {
    return
  }
  const indexArr: string[] = indexContent.split("\n");
  let index = indexArr.findIndex((value, index, obj) => {
    return value.includes('export ')
  })
  indexArr.splice(index, 0, importStatement + '\n');
  
  writeFileSync(indexPath, indexArr.join("\n"), { encoding: "utf8" });
}